#include <drivers/spi.h>
#include <kernel/bus.h>
#include <kernel/initcall.h>
#include <kernel/syslog.h>
#include <iomode.h>


/**
 * @brief spi bus probe function, this function is used to probe the spi device
 * @param dev: device
 * @return: 0: success, -1: failed
 */
static int spi_bus_probe(struct device *dev)
{
    struct spi_device *spidev = to_spi_device(dev);
    struct spi_driver *spidrv = to_spi_driver(dev->driver);

    if(spidrv && spidrv->probe) {
        return spidrv->probe(spidev);
    }

    return 0;
}


/**
 * @brief spi bus remove function, this function is used to remove the spi device
 * @param dev: device
 * @return none
 */
static void spi_bus_remove(struct device *dev)
{
    struct spi_device *spidev = to_spi_device(dev);
    struct spi_driver *spidrv = to_spi_driver(dev->driver);

    if(spidrv->remove) {
        spidrv->remove(spidev);
    }
}


/**
 * spi bus struct, used to register the spi bus
 */
struct bus_type spi_bus = {
    .name = "spi",
    .match = bus_simple_match,
    .probe = spi_bus_probe,
    .remove = spi_bus_remove,
};


/**
 * @brief spi bus register function, this function is used to register the spi bus
 * @param none
 * @return: 0: success, -1: failed
 */
static int spi_bus_register(void)
{
    return bus_register(&spi_bus);
}


bus_initcall(spi_bus_register);


/**
 * @brief get cs pins from device tree
 * @param node: device tree node
 * @param cs_pins: spi_gpio cs pins
 * @return: 0: success, -1: failed
 */
int spi_get_cs_pins(fdt_node_t *node, int16_t *cs_pins)
{
    size_t cs_pin;
    cs_pins[0] = -1, cs_pins[1] = -1, cs_pins[2] = -1, cs_pins[3] = -1, cs_pins[4] = -1;
    
    int cs_size = fdt_get_prop_int_size(node, "cs-gpio");
    if(cs_size <= 0) {
        SPI_SYSLOG_INFO("%s: not found cs pin, will not use 'cs' pins", node->name);
        return 0;
    }

    for(int i = 0; i < cs_size; i++) {
        fdt_read_prop_int_index(node, "cs-gpio", i, &cs_pin);

        if(gpio_request(cs_pin)) {
            SPI_SYSLOG_ERROR("%s: cs gpio request failed", node->name);
            return -1;
        }
        SPI_SYSLOG_INFO("%s: cs gpio(%d) request success", node->name, cs_pin);
        cs_pins[i] = cs_pin;
        gpio_direction_output(cs_pin, IO_HIGH);
    }

    return 0;
}


/**
 * @brief get gpio pin from device tree
 * @param node: device tree node
 * @param pin: spi_gpio pin
 * @return: 0: success, -1: failed
 */
int spi_master_get_gpio_from_fdt(fdt_node_t *node, struct spi_gpio *pin)
{
    size_t clk_pin, miso_pin, mosi_pin, afio = 0;

    if(fdt_read_prop_int(node, "afio", &afio)) {
        SPI_SYSLOG_INFO("%s: not found afio property", node->name);
        return -1;
    }

    if(spi_get_cs_pins(node, pin->cs) < 0) {
        return -1;
    }

    if(fdt_read_prop_int(node, "clk-gpio", &clk_pin)) {
        SPI_SYSLOG_ERROR("%s: not found clk pin", node->name);
        return -1;
    }
    if(gpio_request(clk_pin)) {
        SPI_SYSLOG_ERROR("%s: clk gpio request failed", node->name);
        return -1;
    }
    SPI_DEBUG_TRACE("%s: clk gpio(%d) request success", node->name, clk_pin);
    pin->clk = clk_pin;
    gpio_set_mux(clk_pin, (IO_MODE_SPI_CLK << 8) | afio);

    if(fdt_read_prop_int(node, "miso-gpio", &miso_pin)) {
        SPI_SYSLOG_INFO("%s: not found miso pin", node->name);
        return -1;
    }
    SPI_DEBUG_TRACE("%s: miso gpio(%d) request success", node->name, miso_pin);
    pin->miso = miso_pin;
    gpio_set_mux(miso_pin, (IO_MODE_SPI_MISO << 8) | afio);

    if(fdt_read_prop_int(node, "mosi-gpio", &mosi_pin)) {
        SPI_SYSLOG_INFO("%s: not found mosi pin", node->name);
        return -1;
    }
    SPI_DEBUG_TRACE("%s: mosi gpio(%d) request success", node->name, mosi_pin);
    pin->mosi = mosi_pin;
    gpio_set_mux(mosi_pin, (IO_MODE_SPI_MOSI << 8) | afio);

    return 0;
}


/**
 * @brief get spi device config from device tree
 * @param node: device tree node
 * @param spi_dev: spi device
 * @return: 0: success, -1: failed
 */
int spi_device_get_config_from_fdt(fdt_node_t *node, struct spi_device *spi_dev)
{
    const char *comp = fdt_read_prop_string(node, "compatible");
    if(comp == NULL) {
        SPI_SYSLOG_ERROR("no compatible property");
        return -1;
    }

    size_t addr;
    if(fdt_read_prop_int(node, "reg", &addr)) {
        SPI_SYSLOG_ERROR("%s: not found reg", node->name);
        return -1;
    }
    
    size_t cpol = 0;
    if(fdt_read_prop_int(node, "cpol", &cpol)) {
        SPI_SYSLOG_INFO("%s: not found cpol, default: 0", node->name);
    }

    size_t cpha = 0;
    if(fdt_read_prop_int(node, "cpha", &cpha)) {
        SPI_SYSLOG_INFO("%s: not found cpha, default: 0", node->name);
    }

    const char* duplex = fdt_read_prop_string(node, "half-duplex");
    if(duplex == NULL) {
        SPI_SYSLOG_INFO("%s: not found half-duplex, default: full duplex", node->name);
        spi_dev->duplex = 0;
    }
    else if(strcmp(duplex, "true") == 0) {
        spi_dev->duplex = 1;
    }

    const char* lsb = fdt_read_prop_string(node, "lsb-first");
    if(lsb == NULL) {
        SPI_SYSLOG_INFO("%s: not found lsb-first, default: msb first", node->name);
        spi_dev->lsb = 0;
    }
    else if(strcmp(lsb, "true") == 0) {
        spi_dev->duplex = 1;
    }

    size_t speed = 1000000;
    if(fdt_read_prop_int(node, "speed", &speed)) {
        SPI_SYSLOG_INFO("%s: not found speed, default: 1000000", node->name);
    }
    else {
        SPI_SYSLOG_INFO("%s: speed %d", node->name, speed);
    }

    size_t bus_width = 8;
    if(fdt_read_prop_int(node, "bus-width", &bus_width)) {
        SPI_SYSLOG_INFO("%s: not found bus width, default: 8", node->name);
    }
    else {
        SPI_SYSLOG_INFO("%s: bus width %d", node->name, bus_width);
    }

    SPI_SYSLOG_INFO("mode: %d", (cpol << 1) | cpha);

    spi_dev->device.name = node->name;
    spi_dev->device.dt_node = node;
    spi_dev->device.compatible = comp;
    spi_dev->addr = addr;
    spi_dev->device.driver = NULL;
    spi_dev->mode = (cpol << 1) | cpha;
    spi_dev->bits = bus_width;
    spi_dev->speed = speed;

    return 0;
}
